package com.nulldev.util.uri.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketImpl;
import java.net.SocketImplFactory;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.ThreadPoolExecutor;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.io.FileUtil;
import com.nulldev.util.io.MimeType;
import com.nulldev.util.java.DataSet;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.net.IPUtil;
import com.nulldev.util.net.tcp.LargeDataInputStream;
import com.nulldev.util.net.tcp.LargeDataOutputStream;
import com.nulldev.util.net.tcp.LargePrintWriter;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.other.CharsetUtil;
import com.nulldev.util.threading.fastES.LtqExecutor;
import com.nulldev.util.threading.legacy.ThreadExecutor;
import com.nulldev.util.threading.v2.ThreadManager;
import com.nulldev.util.threading.v2.ThreadPriority;
import com.nulldev.util.threading.v2.nThread;
import com.nulldev.util.threading.v3.ThreadDispatcher;
import com.nulldev.util.uri.URIUtil2;
import com.nulldev.util.uri.http.HTTP;

public class HttpServer {

	private ServerMode opMode = ServerMode.HTTP_1_1;
	private final LinkedList<HttpPage> pages = new LinkedList<HttpPage>();
	private final ServerSocket socket;
	/**
	 * Used only for reading requests
	 */
	@Deprecated
	private final ThreadPoolExecutor exec = ThreadExecutor.get();
	/**
	 * Request executor
	 */
	@Deprecated
	private final ThreadManager manager = new ThreadManager();
	
	private final ThreadDispatcher manager2 = new ThreadDispatcher(new LtqExecutor(196));
	
	private final Logger log = LoggerFactory.getLogger();
	private final int port;
	public HttpServer(final int port) throws Exception {
		this(port, false);
//		this.port = port;
//		this.socket = new ServerSocket(port);		
//		this.exec.execute(new Runnable() {
//			
//			@Override
//			public void run() {
//				while (true) {
//					try {
//						final Socket in = HttpServer.this.socket.accept();
//						final nThread nt = new nThread(new Runnable() {
//							
//							@Override
//							public void run() {
//								try {
//									HttpServer.this.handleRequest(in);
//								} catch (Exception e) {
//									e.printStackTrace();
//								}
//							}
//						});
//						HttpServer.this.manager.addTask(nt);
////						HttpServer.this.exec.execute(new Runnable() {
////							
////							@Override
////							public void run() {
////								try {
////									HttpServer.this.handleRequest(in);
////								} catch (Exception e) {
////									e.printStackTrace();
////								}
////							}
////						}
//						);
//					} catch (Exception e) {
//						e.printStackTrace();
//					}
//				}
//			}
//		});
	}
	
	public HttpServer(final int port, final boolean open) throws Exception {
		this.port = port;
		if (open) {
			this.socket = new ServerSocket();
			this.socket.bind(new InetSocketAddress(IPUtil.getLocalHostLANAddress(), port));
			//System.out.println(this.socket.getInetAddress().getHostAddress());
			this.log.info("Server is now exposed. [ip=" + this.socket.getInetAddress().getHostAddress() + ", port=" + port + "]");
		}else {
			this.socket = new ServerSocket(port);
			this.log.info("Server is now exposed. [ip=" + this.socket.getInetAddress().getHostAddress() + ", port=" + port + "]");
		}
		//this.manager.getLogger().debugEnabled = true;
		this.exec.execute(new Runnable() {
			
			@Override
			public void run() {
				while (true) {
					try {
						final Socket in = HttpServer.this.socket.accept();
						in.setTcpNoDelay(true);
						in.setReceiveBufferSize(Integer.MAX_VALUE);
						in.setSendBufferSize(Integer.MAX_VALUE);
						log.trace("server.recv(), processing request.");
//						HttpServer.this.exec.execute(new Runnable() {
//							
//							@Override
//							public void run() {
//								try {
//									HttpServer.this.handleRequest(in);
//								} catch (Exception e) {
//									e.printStackTrace();
//								}
//							}
//						});
						final nThread nt = new nThread(new Runnable() {
						
							@Override
							public void run() {
								if (HttpServer.this.manager.isFull()) {
									try {
										final LargeDataOutputStream lds = new LargeDataOutputStream(in.getOutputStream());
										final LargePrintWriter out = new LargePrintWriter(lds, true);
										out.println(ServerMode.HTTP_1_1.getType() + " " + 503);
							            out.println("Content-type: text/html");
							            out.println("Server-name: nullUtil: HttpServer");
							            out.println("iWebKit.dstream: enabled");
							        	final String response = "<html><head><h1 style='font-family:sans-serif;font-size:small'>This server is currently busy, try again later.</h1><p style='font-family:sans-serif;font-size:x-small'>Current server load: " + HttpServer.this.manager.getQueue().toString() + "</p></head></html>";
							            out.println("Content-length: " + response.length());
							            out.println("");
							            out.println(response);
							            out.flush();
							            
							            out.close();
									}catch (Exception e) {
										e.printStackTrace();
									}
								}else {
									try {
										HttpServer.this.handleRequest(in);
									} catch (Exception e) {
										e.printStackTrace();
									}
								}
							}
						}, "$HttpServer.Recv", ThreadPriority.NORMAL);
						HttpServer.this.manager2.addTask(nt.toWorker());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});
	}
	
	/**
	 * Opens up a HttpServer. <br>
	 * This method was added in 2.6
	 * @param port [The server port]
	 * @param open [Expose the server to the internet?]
	 * @param enableSSL [Enable SSL? WARNING: EXPERIMENTAL]
	 * @throws Exception
	 */
	public HttpServer(final int port, final boolean open, final boolean enableSSL) throws Exception {
		this.port = port;
		SSLServerSocketFactory ssl = null;
		if (enableSSL) {
			URIUtil2.initSSL();
			final SSLContext context = SSLContext.getInstance("TLSv1.2");
			final TrustManager[] trustAllCerts = new TrustManager[] {
					new X509TrustManager() {
						public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
						}

						public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
						}

						public java.security.cert.X509Certificate[] getAcceptedIssuers() {
							return null;
						}
					}
			};
			context.init(URIUtil2.getKeyManager(), trustAllCerts, new SecureRandom());
			ssl = (SSLServerSocketFactory) context.getServerSocketFactory();
		}
		
		if (open) {
			this.socket = (enableSSL ? ssl.createServerSocket() : new ServerSocket());
			if (this.socket instanceof SSLServerSocket) {
				final SSLServerSocket ssocket = (SSLServerSocket) this.socket;
				ssocket.setEnabledCipherSuites(ssl.getSupportedCipherSuites());
			}
			this.socket.bind(new InetSocketAddress(IPUtil.getLocalHostLANAddress(), port));
			//System.out.println(this.socket.getInetAddress().getHostAddress());
			this.log.info("Server is now exposed. [ip=" + this.socket.getInetAddress().getHostAddress() + ", port=" + port + ",ssl=" + enableSSL + "]");
		}else {
			this.socket = (enableSSL ? ssl.createServerSocket() : new ServerSocket());
			if (this.socket instanceof SSLServerSocket) {
				final SSLServerSocket ssocket = (SSLServerSocket) this.socket;
				ssocket.setEnabledCipherSuites(ssl.getSupportedCipherSuites());
			}
			this.log.info("Server is now exposed. [ip=" + this.socket.getInetAddress().getHostAddress() + ", port=" + port + ",ssl=" + enableSSL + "]");
		}
		//this.manager.getLogger().debugEnabled = true;
		this.exec.execute(new Runnable() {
			
			@Override
			public void run() {
				while (true) {
					try {
						final Socket in = HttpServer.this.socket.accept();
						in.setTcpNoDelay(true);
						in.setReceiveBufferSize(Integer.MAX_VALUE);
						in.setSendBufferSize(Integer.MAX_VALUE);
						log.trace("server.recv(), processing request.");
//						HttpServer.this.exec.execute(new Runnable() {
//							
//							@Override
//							public void run() {
//								try {
//									HttpServer.this.handleRequest(in);
//								} catch (Exception e) {
//									e.printStackTrace();
//								}
//							}
//						});
						final nThread nt = new nThread(new Runnable() {
						
							@Override
							public void run() {
								if (HttpServer.this.manager.isFull()) {
									try {
										final LargeDataOutputStream lds = new LargeDataOutputStream(in.getOutputStream());
										final LargePrintWriter out = new LargePrintWriter(lds, true);
										out.println(ServerMode.HTTP_1_1.getType() + " " + 503);
							            out.println("Content-type: text/html");
							            out.println("Server-name: nullUtil: HttpServer");
							            out.println("iWebKit.dstream: enabled");
							        	final String response = "<html><head><h1 style='font-family:sans-serif;font-size:small'>This server is currently busy, try again later.</h1><p style='font-family:sans-serif;font-size:x-small'>Current server load: " + HttpServer.this.manager.getQueue().toString() + "</p></head></html>";
							            out.println("Content-length: " + response.length());
							            out.println("");
							            out.println(response);
							            out.flush();
							            
							            out.close();
									}catch (Exception e) {
										e.printStackTrace();
									}
								}else {
									try {
										HttpServer.this.handleRequest(in);
									} catch (Exception e) {
										e.printStackTrace();
									}
								}
							}
						}, "$HttpServer.Recv", ThreadPriority.NORMAL);
						//HttpServer.this.manager.addTask(nt);
						HttpServer.this.manager2.addTask(nt.toWorker());
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});
	}
	
	private Charset charset = CharsetUtil.def();
	
	public Charset getCharset() {
		return this.charset;
	}
	
	public void setCharset(final Charset charset) {
		if (charset == null || !Charset.isSupported(charset.name())) {
			return;
		}
		this.charset = charset;
	}
	
	public DataSet<Boolean, HttpPage> canServe(final String[] headers, final LinkedList<String> params, final String ip) {
		return new DataSet<Boolean, HttpPage>(true, null);
	}
	
	public boolean addPage(final HttpPage page) {
		return this.pages.add(page);
	}
	
	public void handleRequest(final Socket args) throws Exception {
		final LargeDataInputStream lis = new LargeDataInputStream(args.getInputStream());
		final InputStreamReader isr = new InputStreamReader(lis);
		final BufferedReader reader = new BufferedReader(isr);
		
		String request = reader.readLine();
		//System.out.println(request);
		final LinkedList<String> headers = new LinkedList<String>();
		headers.add(request);
		
		while (true) {
			final String te = reader.readLine();
			if (te == null || te.length() == 0 || te.equals("")) {
				break;
			}
			//ArrayUtils.printLln(headers);
			headers.add(te);
		}
		
		final String[] request_headers = request.split(" ");
		//ArrayUtils.printLln(headers);
		
		final LargeDataOutputStream lds = new LargeDataOutputStream(args.getOutputStream());
		final LargePrintWriter out = new LargePrintWriter(lds, true);
		
		final DataSet<Boolean, HttpPage> s = canServe(request_headers, headers, args.getInetAddress().getHostAddress());
		//ArrayUtils.println(request_headers[1].split("\\?|&"));
		if (!s.getA()) {
			final String toFind = request_headers[1].split("\\?|&")[0];
			if (s.getB() != null) {
				out.println(this.opMode.getType() + " " + HTTP.OK);
				final HttpPage page = s.getB();
                if (page.getID().endsWith(".js")) {
                	out.println("Content-type: application/javascript");
                }else if (page.getID().endsWith(".html")){
                	out.println("Content-type: text/html");
                }else if (page.getID().endsWith(".png")) {
                	out.println("Content-type: image/png");
                }else if (page.getID().endsWith(".jpg")) {
                	out.println("Content-type: image/jpg");
                }else if (page.getID().endsWith(".gif")) {
                	out.println("Content-type: image/gif");
                }else if (page.getID().endsWith(".mp4")) {
                	out.println("Content-type: video/mp4");
                }else {
                	//log.warn("Content-type: Unknown format.");
                }
                out.println("Server-name: nullUtil: HttpServer");
                
                allowCrossOrigin(page, out, headers);
                
                if (page.sendRaw()) {
                	final byte[] data = FileUtil.read(page.getPath());
                    out.println("Content-length: " + data.length);
                    out.println("");
                    try {
                    	 args.getOutputStream().write(data, 0, data.length);
                    }catch (Exception e) {
                    	 log.error("Raw data write failed!", e);
                    }
                }else {
                	//System.out.println(page.isOverBytes());
                	if (page.isOverBytes()) {
                        final byte[] output = page.handleRequest_Alt(request_headers[1], args.getInetAddress().toString(), RequestType.GET);
                        out.println("Content-length: " + output.length);
                        out.println("");
                        //out.println(output);
                        try {
	                        args.getOutputStream().write(output, 0, output.length);
                        }catch (Exception e) {
                        	 log.error("Raw data write failed!", e);
                        }
                	}else {
                        final String output = page.handleRequest(request_headers[1], args.getInetAddress().toString(), RequestType.GET);
                        if (output == null) {
                        	final String err = "<html><head><h1 style='font-family:sans-serif;'>500: Internal Error occured within page.</h1><p style=\"font-family:sans-serif\">nullUtil Http Server 1.1 | Delivered by Thread #" + Thread.currentThread().getId()+ ".</p></head></html>";;
	                        out.println("Content-length: " + err.length());
	                        out.println("");
	                        out.println(err);
                        }else {
                        	if (output.length() > 64000) {
                        		final byte[] o0 = output.getBytes(charset);
                        	   	out.println("Content-length: " + o0.length);
   	                        	out.println("");
   	                        	try {
   	                        		args.getOutputStream().write(o0, 0, o0.length);
   	                        	}catch (Exception e) {
   	                        		log.error("Raw data write failed!", e);
   	                        	}	
                        	}else {
                        		out.println("Content-length: " + output.length());
    	                        out.println("");
    	                        out.println(output);
                        	}
                        }
                	}
                }
                out.flush();
                
                out.close();
	            reader.close();
	            isr.close();
	            args.close();
	            
	            return;
                
			}else {
				out.println(this.opMode.getType() + " " + HTTP.OK);
	            out.println("Content-type: text/html");
	            out.println("Server-name: nullUtil: HttpServer");
	            out.println("iWebKit.dstream: enabled");
	        	final String response = "<html><head><h1 style='font-family:sans-serif;font-size:small'>Somehow your not allowed to use me.</h1></head></html>";
	            out.println("Content-length: " + response.length());
	            out.println("");
	            out.println(response);
	            out.flush();
	            
	            out.close();
	            reader.close();
	            isr.close();
	            args.close();
	            
	            return;
			}
		}
		
		if (request_headers[0].equals("GET")) {
			final FinalBypass<Boolean> bool = new FinalBypass<Boolean>(false);
	        if (this.opMode != ServerMode.SPOOF_HTTP2) {
	        	out.println(this.opMode.getType() + " " + HTTP.OK);
	        }else {
	        	out.println(ServerMode.HTTP_1_1.getType() + " 405");
	        	bool.setObject(true);
	        }
	        if (bool.getObject()) {
	            out.println("Content-type: text/html");
	            out.println("Server-name: nullUtil: HttpServer");
	            out.println("iWebKit.dstream: enabled");
	        	final String response = "<html><head><h1 style='font-family:sans-serif;font-size:small'>Invalid server configuration, HTTP/2 is not supported. [It's a bad thing anyway.]</h1></head></html>";
	            out.println("Content-length: " + response.length());
	            out.println("");
	            out.println(response);
	            out.flush();
	        }else {
	            if (pages.isEmpty()) {
	                out.println("Content-type: text/html");
	                out.println("Server-name: nullUtil: HttpServer");
	                out.println("iWebKit.dstream: enabled");
	            	final String response = "<html><head><h1 style='font-family:sans-serif;'>It works! But there's no pages added.</h1></head></html>";
	                out.println("Content-length: " + response.length());
	                out.println("");
	                out.println(response);
	                out.flush();
	            }else {
	            	final String toFind = request_headers[1].split("\\?|&")[0];
	            	HttpPage page = null;
	        		if (request_headers[1].contains("?")) {
	        			for (final HttpPage pge : this.pages) {
	        	    		if (pge.getID().equals(request_headers[1].substring(0, request_headers[1].indexOf("?")))) {
	        	    			page = pge;
	        	    		}
	        			}
	        		}else {
	        			for (final HttpPage pge : this.pages) {
	        	    		if (pge.getID().equals(request_headers[1])) {
	        	    			page = pge;
	        	    		}
	        			}
	        		}
	            	if (page == null) {
	                    out.println("Content-type: text/html");
	                    out.println("Server-name: nullUtil: HttpServer");
	                    out.println("iWebKit.dstream: enabled");
	                	final String response = "<html><head><h1 style='font-family:sans-serif;'>404: Page Not Found</h1><p style=\"font-family:sans-serif\">nullUtil Http Server 1.1 | Delivered by Thread #" + Thread.currentThread().getId()+ ".</p></head></html>";
	                    out.println("Content-length: " + response.length());
	                    out.println("");
	                    out.println(response);
	                    out.flush();
	            	}else {
	                    if (page.getID().endsWith(".js")) {
	                    	out.println("Content-type: application/javascript");
	                    }else if (page.getID().endsWith(".html")){
	                    	out.println("Content-type: text/html");
	                    }else if (page.getID().endsWith(".png")) {
	                    	out.println("Content-type: image/png");
	                    }else if (page.getID().endsWith(".jpg")) {
	                    	out.println("Content-type: image/jpg");
	                    }else if (page.getID().endsWith(".gif")) {
	                    	out.println("Content-type: image/gif");
	                    }else if (page.getID().endsWith(".mp4")) {
	                    	out.println("Content-type: video/mp4");
	                    }else if (page.isCustomMime()) {
	                    	out.println("Content-type: " + page.getMime());
	                    }else {
	                    	try {
	                    		if (page.sendRaw()) {
	                    			final String mime = URIUtil2.FileIdentifier.getMimeType(page.getPath());
	                    			if (!mime.equals("file/unknown")) {
	                    				out.println("Content-type: " + mime);
	                    			}
	                    		}
	                    	}catch (Exception e) {
	                    		log.debug("Warning: Content-type: Unknown format.");
	                    	}
	                    }
	                    out.println("Server-name: nullUtil: HttpServer");
	                    out.println("iWebKit.dstream: enabled");
	                    
	                    allowCrossOrigin(page, out, headers);
	                    
	                    if (page.sendRaw()) {
	                    	final byte[] data = FileUtil.read(page.getPath());
	                        out.println("Content-length: " + data.length);
	                        out.println("");
	                        try {
	                        	 args.getOutputStream().write(data, 0, data.length);
	                        }catch (Exception e) {
	                        	 log.error("Raw data write failed!", e);
	                        }
	                    }else {
	                    	//System.out.println(page.isOverBytes());
	                    	if (page.isOverBytes()) {
		                        final byte[] output = page.handleRequest_Alt(request_headers[1], args.getInetAddress().toString(), RequestType.GET);
		                        out.println("Content-length: " + output.length);
		                        out.println("");
		                        //out.println(output);
		                        try {
			                        args.getOutputStream().write(output, 0, output.length);
		                        }catch (Exception e) {
		                        	 log.error("Raw data write failed!", e);
		                        }
	                    	}else {
		                        final String output = page.handleRequest(request_headers[1], args.getInetAddress().toString(), RequestType.GET);
		                        if (output == null) {
		                        	final String err = "<html><head><h1 style='font-family:sans-serif;'>500: Internal Error occured within page.</h1><p style=\"font-family:sans-serif\">nullUtil Http Server 1.1 | Delivered by Thread #" + Thread.currentThread().getId()+ ".</p></head></html>";;
			                        out.println("Content-length: " + err.length());
			                        out.println("");
			                        out.println(err);
		                        }else {
		                        	final byte[] o0 = output.getBytes(charset);
	                        	   	out.println("Content-length: " + o0.length);
	    	                        out.println("");
	    	                        try {
	    	                        	args.getOutputStream().write(o0, 0, o0.length);
	    	                        	args.getOutputStream().flush();
	    	                        }catch (Exception e) {
	    	                        	if (!e.getMessage().equals("Broken pipe (Write failed)")) {
	    	                        		log.error("Raw data write failed!", e);
	    	                        	}
	    	                        }	
//		                        	System.out.println(output);
//		                        	System.out.println(output.getBytes().length);
//		                            if (output.getBytes().length > 64000) {
//		                        		final byte[] o0 = output.getBytes();
//		                        	   	out.println("Content-length: " + o0.length);
//		    	                        out.println("");
//		    	                        try {
//		    	                        	args.getOutputStream().write(o0, 0, o0.length);
//		    	                        	args.getOutputStream().flush();
//		    	                        }catch (Exception e) {
//		    	                        	log.error("Raw data write failed!", e);
//		    	                        }	
//		                        	}else {
//		                        		out.println("Content-length: " + output.length());
//		    	                        out.println("");
//		    	                        out.println(output);
//		                        	}
		                        }
	                    	}
	                    }
	                    out.flush();
	            	}
	            }
	        }
	    // POST
		}else {
			if (request_headers.length < 2) {
				out.println(ServerMode.HTTP_1_1.getType() + " 404");
				out.flush();
				out.close();
				return;
			}
			final String toFind = request_headers[1].split("\\?|&")[0];
        	HttpPage page = null;
    		if (request_headers[1].contains("?")) {
    			for (final HttpPage pge : this.pages) {
    	    		if (pge.getID().equals(request_headers[1].substring(0, request_headers[1].indexOf("?")))) {
    	    			page = pge;
    	    		}
    			}
    		}else {
    			for (final HttpPage pge : this.pages) {
    	    		if (pge.getID().equals(request_headers[1])) {
    	    			page = pge;
    	    		}
    			}
    		}
        	if (page == null) {
                out.println("Content-type: text/html");
                out.println("Server-name: nullUtil: HttpServer");
            	final String response = "<html><head><h1 style='font-family:sans-serif;'>404: Page Not Found</h1><p style=\"font-family:sans-serif\">nullUtil Http Server 1.1 | Delivered by Thread #" + Thread.currentThread().getId()+ ".</p></head></html>";
                out.println("Content-length: " + response.length());
                out.println("");
                out.println(response);
                out.flush();
        	}else {
                if (page.getID().endsWith(".js")) {
                	out.println("Content-type: application/javascript");
                }else if (page.getID().endsWith(".html")){
                	out.println("Content-type: text/html");
                }else if (page.getID().endsWith(".png")) {
                	out.println("Content-type: image/png");
                }else if (page.getID().endsWith(".jpg")) {
                	out.println("Content-type: image/jpg");
                }else if (page.getID().endsWith(".gif")) {
                	out.println("Content-type: image/gif");
                }else if (page.getID().endsWith(".mp4")) {
                	out.println("Content-type: video/mp4");
                }else {
                	log.debug("Warning: Content-type: Unknown format.");
                }
                out.println("Server-name: nullUtil: HttpServer");
                
                allowCrossOrigin(page, out, headers);
                
                if (page.sendRaw()) {
                	final byte[] data = FileUtil.read(page.getPath());
                    out.println("Content-length: " + data.length);
                    out.println("");
                    try {
                    	 args.getOutputStream().write(data, 0, data.length);
                    }catch (Exception e) {
                    	 log.error("Raw data write failed!", e);
                    }
                }else {
                	//System.out.println(page.isOverBytes());
                	if (page.isOverBytes()) {
                        final byte[] output = page.handleRequest_Alt(request_headers[1], args.getInetAddress().toString(), RequestType.POST);
                        out.println("Content-length: " + output.length);
                        out.println("");
                        //out.println(output);
                        try {
	                        args.getOutputStream().write(output, 0, output.length);
                        }catch (Exception e) {
                        	 log.error("Raw data write failed!", e);
                        }
                	}else {
                        final String output = page.handleRequest(request_headers[1], args.getInetAddress().toString(), RequestType.POST);
                        final byte[] o0 = output.getBytes();
                	   	out.println("Content-length: " + o0.length);
                        out.println("");
                        try {
                        	args.getOutputStream().write(o0, 0, o0.length);
                        	args.getOutputStream().flush();
                        }catch (Exception e) {
                        	log.error("Raw data write failed!", e);
                        }	
//                    	System.out.println(output.getBytes().length);
//                        if (output.getBytes().length > 64000) {
//                    		final byte[] o0 = output.getBytes();
//                    	   	out.println("Content-length: " + o0.length);
//	                        out.println("");
//	                        try {
//	                        	args.getOutputStream().write(o0, 0, o0.length);
//	                        	args.getOutputStream().flush();
//	                        }catch (Exception e) {
//	                        	log.error("Raw data write failed!", e);
//	                        }	
//                    	}else {
//                    		out.println("Content-length: " + output.length());
//	                        out.println("");
//	                        out.println(output);
//                    	}
                	}
                }
                out.flush();
        	}
		}
        
        out.close();
        reader.close();
        isr.close();
        args.close();
	}
	
//	for (final HttpPage pge : this.pages) {
//	if (toFind.contains("?")) {
//		if (pge.getID().equals(toFind.substring(0, toFind.indexOf("?")))) {
//			page = pge;
//		}
//	}else {
//		if (pge.getID().equals(toFind)) {
//			page = pge;
//		}
//	}
//}
	
	private boolean enforceGlobalCrossOrigin = false;
	
	public boolean enforceGlobalCrossOrigin() {
		return this.enforceGlobalCrossOrigin;
	}
	
	public void enforceGlobalCrossOrigin(final boolean value) {
		this.enforceGlobalCrossOrigin = value;
	}
	
	protected void allowCrossOrigin(final HttpPage page, final LargePrintWriter out, final LinkedList<String> headers) throws Exception {
		if (page.canAccessCrossOrigin() || this.enforceGlobalCrossOrigin) {
//			String origin = null;
//			for (final String header : headers) {
//				try {
//					if (header.startsWith("origin") || header.startsWith("Origin")) {
//						origin = header.split(": ")[1];
//						break;
//					}
//				}catch (Exception e) {
//					origin = null;
//					break;
//				}
//			}
//			if (origin == null) {
//				out.println("Access-Control-Allow-Origin: *");
//			}else {
//				out.println("Access-Control-Allow-Origin: " + origin);
//			}
			out.println("Access-Control-Allow-Origin: *");
			out.println("Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE");
			out.println("Access-Control-Allow-Headers: *");
			out.println("Access-Control-Max-Age: 36000");
			out.println("Access-Control-Allow-Credentials: true");
		}
	}

	public void addFromFolder(final File file) throws Exception {
		final ArrayList<File> files = FileUtil.listFilesForFolder(file);
		for (final File f : files) {
			if (!f.isDirectory()) {
				final String loc = f.getPath().replaceAll(file.getPath(), "");
				System.out.println(loc);
				final HttpPage s = new HttpPage(loc, f) {
					
					@Override
					public String handleRequest(String args, String ip, RequestType type) {
						return this.getData();
					}

					@Override
					public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
						return null;
					}
				};
				s.loadFromFile(f);
				this.addPage(s);
			}
		}
	}
	
	public void addFromFolderRAW(final File file) throws Exception {
		final ArrayList<File> files = FileUtil.listFilesForFolder(file);
		for (final File f : files) {
			if (!f.isDirectory()) {
				final String loc = f.getPath().replaceAll(file.getPath(), "");
				//System.out.println(loc);
				final HttpPage s = new HttpPage(loc, f) {
					
					@Override
					public String handleRequest(String args, String ip, RequestType type) {
						return this.getData();
					}

					@Override
					public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
						return null;
					}
				};
				//s.loadFromFile(f);
				s.setSendRaw(true);
				this.addPage(s);
			}
		}
	}
	
	public int addFromFolderRAWI(final File file) {
		int cnt = 0;
		final ArrayList<File> files = FileUtil.listFilesForFolder(file);
		for (final File f : files) {
			if (!f.isDirectory()) {
				final String loc = f.getPath().replaceAll(file.getPath(), "");
				//System.out.println(loc);
				final HttpPage s = new HttpPage(loc, f) {
					
					@Override
					public String handleRequest(String args, String ip, RequestType type) {
						return this.getData();
					}

					@Override
					public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
						return null;
					}
				};
				//s.loadFromFile(f);
				s.setSendRaw(true);
				this.addPage(s);
				cnt += 1;
			}
		}
		return cnt;
	}
	
	public void addFromFolder(final File file, final String[] excluded) throws Exception {
		final ArrayList<File> files = FileUtil.listFilesForFolder(file);
		for (final File f : files) {
			if (!f.isDirectory()) {
				final String loc = f.getPath().replaceAll(file.getPath(), "");
				//System.out.println(loc);
				if (!ArrayUtils.StringArrayUtils.containsE(excluded, loc)) {
					final HttpPage s = new HttpPage(loc, f) {
						
						@Override
						public String handleRequest(String args, String ip, RequestType type) {
							return this.getData();
						}

						@Override
						public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
							return null;
						}
					};
					s.loadFromFile(f);
					this.addPage(s);
				}
			}
		}
	}

	public void addFileRAW(final File file) throws Exception {
		final HttpPage s = new HttpPage("/" + file.getName(), file) {
			
			@Override
			public String handleRequest(String args, String ip, RequestType type) {
				return this.getData();
			}

			@Override
			public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
				return null;
			}
		};
		//s.loadFromFile(file);
		s.setSendRaw(true);
		this.addPage(s);
	}
	
	public void addFileRAW(final String name, final File file) throws Exception {
		final HttpPage s = new HttpPage("/" + name, file) {
			
			@Override
			public String handleRequest(String args, String ip, RequestType type) {
				return this.getData();
			}

			@Override
			public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
				return null;
			}
		};
		//s.loadFromFile(file);
		s.setSendRaw(true);
		s.setMime(MimeType.fromFile(file).getType());
		this.addPage(s);
	}
	
	public void addFileString(final String name, final String file) throws Exception {
		final HttpPage s = new HttpPage("/" + name) {
			
			@Override
			public String handleRequest(String args, String ip, RequestType type) {
				return file;
			}

			@Override
			public byte[] handleRequest_Alt(String args, String ip, RequestType type) {
				return null;
			}
		};
		//s.loadFromFile(file);
		//s.setSendRaw(true);
		this.addPage(s);
	}

	public void listPages() {
		for (final HttpPage page : (LinkedList<HttpPage>) this.pages.clone()) {
			System.out.println(page.getID());
		}
	}

	public Logger getLogger() {
		return this.log;
	}
	
	public ServerMode getMode() {
		return this.opMode;
	}
	
	public void setMode(final ServerMode mode) {
		if (mode == null) {
			return;
		}
		this.opMode = mode;
	}

	public void printCoreData() {
		this.log.info("Server is running at " + socket.getInetAddress() + ", " + socket.getLocalPort());
	}

	public int getPort() {
		return this.port;
	}
	
	public String getIP() {
		return this.socket.getInetAddress().getHostAddress();
	}
	
	public HttpPage getPage(final String string) {
		for (final HttpPage p : (LinkedList<HttpPage>) this.pages.clone()) {
			if (p.getID().equals(string)) {
				return p;
			}
		}
		return null;
	}
}
